﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using SmlEngine.Sprites.Base;
using SmlEngine.Sprites.Camera;
using SmlEngine.Sprites.Settings;
using SmlEngine.Sprites.Players;
using SmlEngine.UI.Input;
using SmlEngine.UI.Managers;

namespace SmlEngine.Sprites.Collections
{
    public class Level : IName // (cel) Needed to remove sealed modifier since events don't play well with sealed classes (got this compiler error: http://msdn.microsoft.com/en-us/library/exaxze9s(v=vs.71).aspx)
    {
        bool mode; //Auto-scroll, manual.

        #region Properties

        public string Name { get; set; }

        public InputManager Input { get; set; }

        public BackgroundSet Background { get; private set; }
        public IList<Layer> Layers { get; private set; }
        public IList<Player> Players { get; private set; } //May use PlayerCollection

        public Camera2D Camera { get; private set; }

        public int Section { get; set; }

        public int Width { get; set; }
        public int Height { get; set; }

        public int Score { get; set; }

        public int Time { get; set; }
        public float TimeRemaining { get; set; }

        public bool IsRunning { get; set; }

        public Axis Wrap { get; set; }
        public Axis Exit { get; set; }

        public event LevelClearedEventHandler LevelCleared;

        #endregion

        #region Methods

        #region Constructors

        public Level()
        {
            Camera = new Camera2D();
            Background = new BackgroundSet();
            Layers = new List<Layer>(4);
            Players = new List<Player>(4);
        }

        public Level(Stream stream)
            : this()
        {
            LevelLoader.ReloadLevel(this, stream);
        }

        #endregion

        #region Loading

        public void Reload(Stream stream)
        {
            LevelLoader.ReloadLevel(this, stream);
        }

        public static Level FromFile(Stream stream)
        {
            Level level = new Level();
            LevelLoader.ReloadLevel(level, stream);
            return level;
        }

        //Can be refactored
        internal static class LevelLoader
        {
            private static SpriteType type;
            private static Tile tile;
            private static Npc npc;
            private static int layerIndex;

            private static int x;

            private static bool isLayer;
            private static bool isBackground;

            private static Level level;

            internal static void ReloadLevel(Level level, Stream stream)
            {
                isLayer = true;
                LevelLoader.level = level;

                //Testing
                Player player = new Player(level);
                level.Players.Add(player);
                
                bool reachedBody = false;
                int itemIndex = 0;
                StringBuilder builder = new StringBuilder();

                for (int i = 0; i < stream.Length; i++)
                {
                    int data = stream.ReadByte();

                    if (data == -1) //Finished reading stream.
                        return;

                    char character = (char)data;

                    if (data == '\n' || data == '\r' || data == ' ')
                        continue;

                    bool isSplitter = character == '|';

                    reachedBody = reachedBody || isSplitter;

                    if (!reachedBody)
                    {
                        //Read head data
                        if (character == ',')
                        {
                            ParseHead(level, itemIndex, builder.ToString());
                            builder.Clear();
                            itemIndex++;
                        }
                        else
                        {
                            builder.Append(character);
                        }
                    }
                    else
                    {
                        if (isSplitter) //Finish reading head data
                        {
                            ParseHead(level, itemIndex, builder.ToString());
                            builder.Clear();
                            itemIndex = 0;
                        } //Read body data
                        else if (character == ':') //Reached next item
                        {
                            ParseItem(itemIndex, builder.ToString());

                            if (type == SpriteType.Tile)
                                level.Layers[layerIndex].StaticSprites.Add(tile.Clone(), Visibility.Visible);
                            else if (type == SpriteType.Npc)
                                level.Layers[layerIndex].Sprites.Add(npc, Visibility.Visible);

                            builder.Clear();
                            itemIndex = 0;
                        }
                        else if (character == ',')
                        {
                            ParseItem(itemIndex, builder.ToString());
                            builder.Clear();
                            itemIndex++;
                        }
                        else
                        {
                            builder.Append(character);
                        }
                    }
                }

                //Testing
                level.Camera.Position = -new Vector2(level.Width / 2, level.Height / 2);
            }

            internal static void ParseHead(Level level, int index, string value)
            {
                switch (index)
                {
                    case 0:
                        level.Name = value;
                        return;

                    case 1:
                        level.Width = Convert.ToInt32(value);
                        return;

                    case 2:
                        level.Height = Convert.ToInt32(value);
                        return;

                    default:

                        if (value[0] == '*')
                        {
                            if (isLayer)
                            {
                                isLayer = false;
                                isBackground = true;
                            }

                            value = value.Substring(1, value.Length - 1);
                        }

                        if (isLayer)
                        {
                            Layer layer = new Layer(value);
                            layer.Visibility = Visibility.Visible;
                            level.Layers.Add(layer);
                            return;
                        }
                        else if (isBackground)
                        {
                            BackgroundLayer layer = BackgroundSettings.GetBehaviour(Convert.ToInt32(value)).Clone();
                            level.Background.Layers.Add(layer);
                            return;
                        }
                        return;
                }
            }

            internal static void ParseItem(int index, string value)
            {
                switch (index)
                {
                    case 0:
                        type = (SpriteType)Convert.ToInt32(value);
                        return;

                    case 1:
                        CreateSprite(Convert.ToInt32(value));
                        return;

                    case 2:
                        layerIndex = Convert.ToInt32(value);
                        return;

                    case 3:
                        x = Convert.ToInt32(value);
                        return;

                    case 4:
                        if (type == SpriteType.Tile)
                            tile.Collision.Origin += new Vector2(x, Convert.ToInt32(value));
                        else
                            GetSpriteByType().Position = new Vector2(x, Convert.ToInt32(value));

                        return;

                    case 5:
                        
                        return;

                    //Load other settings here.
                }
            }

            internal static SpriteBase GetSpriteByType()
            {
                switch (type)
                {
                    case SpriteType.Tile:
                        return tile;

                    case SpriteType.Npc:
                        return npc;

                    default:
                        return null;
                }
            }

            internal static void CreateSprite(int index)
            {
                switch (type)
                {
                    case SpriteType.Tile:
                        tile = TileSettings.GetBehaviour(index);
                        tile.Id = index;
                        return;

                    case SpriteType.Npc:
                        npc = new Npc(level);
                        npc.Id = index;
                        return;
                }
            }
        }

        #endregion

        #region Update and Draw

        public void Update(GameTime gameTime)
        {
            if(Input.IsNewKeyMapPress(InputSettings.Cancel))
                IsRunning = !IsRunning;

            if (Input.IsNewKeyPress(Keys.X))
                OnLevelCleared(new LevelClearedEventArgs(0)); // temporary

            if (IsRunning)
            {
                if (TimeRemaining > 0)
                {
                    TimeRemaining -= gameTime.GetElapsedSeconds();

                    if (TimeRemaining <= 0)
                        Timeout();
                }

                for (int i = 0; i < Layers.Count; i++)
                {
                    Layers[i].Update(gameTime);
                }

                for (int i = 0; i < Players.Count; i++)
                {
                    Players[i].Update(gameTime);
                }
            }

            //Testing code

            Vector2 offset = new Vector2();

            if (Input.IsNewKeyPress(Keys.R))
                mode = !mode;

            //if (Input.IsNewKeyPress(Keys.H))
                //Camera.Zoom *= 1.5f;

            //if (Input.IsNewKeyPress(Keys.J))
                //Camera.Zoom /= 1.5f;

            if (mode)
            {
                int speed = Input.Current.KeyboardState.IsKeyDown(Keys.LeftShift) ? 10 : 2;

                if (Input.Current.KeyboardState.IsKeyDown(Keys.Q))
                    offset.X -= speed;
                else if (Input.Current.KeyboardState.IsKeyDown(Keys.D))
                    offset.X += speed;
                else if (Input.Current.KeyboardState.IsKeyDown(Keys.W))
                    offset.Y -= speed;
                else if (Input.Current.KeyboardState.IsKeyDown(Keys.S))
                    offset.Y += speed;
            }
            else
            {
                var player = Players[0];
                offset = player.ProjectedPosition - player.Position;
            }

            Scroll(gameTime, offset);

            //End of testing code
        }

        public void Scroll(GameTime gameTime, Vector2 offset)
        {
            Camera.Move(offset);
            Background.Update(gameTime, offset);
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            spriteBatch.End();

            Background.Draw(gameTime, spriteBatch, Section);

            Matrix transform = Camera.GetTransformation();

            spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, transform);

            for (int i = 0; i < Layers.Count; i++)
            {
                Layers[i].Draw(gameTime, spriteBatch);
            }

            for (int i = 0; i < Players.Count; i++)
            {
                Players[i].Draw(gameTime, spriteBatch);
            }

            spriteBatch.End();

            spriteBatch.Begin();
        }

        #endregion

        #region Public Methods

        public void Start()
        {
            IsRunning = true;
        }

        public void Stop()
        {
            IsRunning = false;
        }

        public void Timeout()
        {

        }

        #endregion

        protected virtual void OnLevelCleared(LevelClearedEventArgs e)
        {
            if (LevelCleared != null)
                LevelCleared(this, e);
        }
        #endregion
    }

    public sealed class LevelClearedEventArgs : EventArgs
    {
        /// <summary>
        /// Used to indicate which direction the path tiles should reveal on the related overworld.
        /// </summary>
        /// <remarks>If no world or project is associated to this level, than this field will not be used.</remarks>
        public int exitIndex;

        public LevelClearedEventArgs(int c_exitIndex)
        {
            exitIndex = c_exitIndex;
        }
    }

    public delegate void LevelClearedEventHandler(object sender, LevelClearedEventArgs e);
}
